// =====================================================================
//
//  This file is part of the Microsoft Dynamics CRM SDK code samples.
//
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//  This source code is intended only as a supplement to Microsoft
//  Development Tools and/or on-line documentation.  See these other
//  materials for detailed information regarding Microsoft code samples.
//
//  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//
// =====================================================================
//using Microsoft.Xrm.Sdk.Core;
namespace Microsoft.Xrm.Sdk
{
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Diagnostics.CodeAnalysis;
	using System.Runtime.Serialization;
	using Microsoft.Xrm.Sdk.Temporary;

	/// <summary>
	/// XRM Collection object
	/// </summary>
	/// <typeparam name="T">The type of elements in the collection</typeparam>
	//[Serializable]
	public class DataCollection<T> : System.Collections.ObjectModel.Collection<T>
	{
		public DataCollection()
			: base()
		{
		}


		internal DataCollection(IList<T> list)
			: base()
		{
			this.AddRange(list);
		}

		/// <summary>
		/// Add items to the collection
		/// </summary>
		/// <param name="items"></param>
		public void AddRange(params T[] items)
		{
			if (items != null)
			{
				this.AddRange((IEnumerable<T>)items);
			}
		}

		/// <summary>
		/// Add items from another collection
		/// </summary>
		/// <param name="items"></param>
		public void AddRange(IEnumerable<T> items)
		{
			if (null != items)
			{
				foreach (T item in items)
				{
					this.Add(item);
				}
			}
		}

		/// <summary>
		/// Converts the collection into an array
		/// </summary>
		/// <returns>Array for the collection</returns>
		public T[] ToArray()
		{
			T[] array = new T[this.Count];
			this.CopyTo(array, 0);

			return array;
		}
	}

	/// <summary>
	/// Base class for Dictionary types in XRM SDK
	/// </summary>
	/// <typeparam name="TKey"></typeparam>
	/// <typeparam name="TValue"></typeparam>    
	//[Serializable]
	public abstract class DataCollection<TKey, TValue> : IDictionary<TKey, TValue>
	{
		protected internal DataCollection()
		{
		}

		/// <summary>
		/// Adds an item to the collection
		/// </summary>
		/// <param name="item">Item to be added</param>
		public void Add(KeyValuePair<TKey, TValue> item)
		{
			if (item.Key != null && item.Value != null)
			{
				CheckIsReadOnly();
				((ICollection<KeyValuePair<TKey, TValue>>)this._innerDictionary).Add(item);
			}
		}

		/// <summary>
		/// Adds the given items to the collection
		/// </summary>
		/// <param name="items"></param>
		public void AddRange(params KeyValuePair<TKey, TValue>[] items)
		{
			CheckIsReadOnly();
			this.AddRange(items as IEnumerable<KeyValuePair<TKey, TValue>>);
		}

		/// <summary>
		/// Adds the given items to the collection
		/// </summary>
		/// <param name="items"></param>
		[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Allows AddRange to be used for multiple types of enumerable items")]
		public void AddRange(IEnumerable<KeyValuePair<TKey, TValue>> items)
		{
			if (null != items)
			{
				CheckIsReadOnly();
				ICollection<KeyValuePair<TKey, TValue>> pairCollection = this._innerDictionary;
				foreach (KeyValuePair<TKey, TValue> item in items)
				{
					pairCollection.Add(item);
				}
			}
		}

		/// <summary>
		/// Adds an item to the collection
		/// </summary>
		/// <param name="key">Key of the item</param>
		/// <param name="value">Value to be added</param>
		public void Add(TKey key, TValue value)
		{
			CheckIsReadOnly();
			this._innerDictionary.Add(key, value);
		}

		/// <summary>
		/// Property accessor value
		/// </summary>
		public TValue this[TKey key]
		{
			get
			{
				return this._innerDictionary[key];
			}

			set
			{
				CheckIsReadOnly();
				this._innerDictionary[key] = value;
			}
		}

		/// <summary>
		/// Clears the items from the collection
		/// </summary>
		public void Clear()
		{
			CheckIsReadOnly();
			this._innerDictionary.Clear();
		}

		/// <summary>
		/// Checks if an item exists in the collection
		/// </summary>
		/// <param name="key">Key for the item</param>
		/// <returns>True if the item is contained in the collection</returns>
		public bool Contains(TKey key)
		{
			return this._innerDictionary.ContainsKey(key);
		}

		/// <summary>
		/// Determines whether the collection contains a specific value.
		/// </summary>
		/// <param name="item">The object to locate in the collection</param>
		/// <returns>True if item is found in the collection; otherwise, false.</returns>
		public bool Contains(KeyValuePair<TKey, TValue> key)
		{
			return this._innerDictionary.Contains(key);
		}

		/// <summary>
		/// Gets the value associated with the specified key.
		/// </summary>
		/// <param name="key">Key for the item</param>
		/// <param name="value">When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized.</param>
		/// <returns>True if the DataCollection contains an element with the specified key; otherwise, false.</returns>
		public bool TryGetValue(TKey key, out TValue value)
		{
			return this._innerDictionary.TryGetValue(key, out value);
		}

		/// <summary>
		/// Copies the elements of the collection to an Array, starting at a particular Array index.
		/// </summary>
		/// <param name="array">The one-dimensional Array that is the destination of the elements copied from the collection. The Array must have zero-based indexing.</param>
		/// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
		public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
		{
			_innerDictionary.CopyTo(array, arrayIndex);
		}


		/// <summary>
		/// Checks if an item exists in the dictionary
		/// </summary>
		/// <param name="key">Key for the item</param>
		/// <returns>True if the item is contained in the dictionary</returns>
		public bool ContainsKey(TKey key)
		{
			return this._innerDictionary.ContainsKey(key);
		}

		/// <summary>
		/// Removes an item from the collection
		/// </summary>
		/// <param name="key"></param>
		/// <returns>True if the item was removed</returns>
		public bool Remove(TKey key)
		{
			CheckIsReadOnly();
			return this._innerDictionary.Remove(key);
		}

		/// <summary>
		/// Removes the first occurrence of a specific object from the collection.
		/// </summary>
		/// <param name="item">The object to remove from the collection.</param>
		/// <returns>True if item was successfully removed from the collection; otherwise, false. This method also returns false if item is not found in the original collection.</returns>
		public bool Remove(KeyValuePair<TKey, TValue> item)
		{
			CheckIsReadOnly();
			return this._innerDictionary.Remove(item);
		}

		/// <summary>
		/// Number of elements in the collection
		/// </summary>
		public int Count
		{
			get
			{
				return this._innerDictionary.Count;
			}
		}

		/// <summary>
		/// Gets an ICollection<T> containing the keys of the dictionary.
		/// </summary>
		public ICollection<TKey> Keys
		{
			get
			{
				return _innerDictionary.Keys;
			}
		}

		/// <summary>
		/// Gets an ICollection<T> containing the values in the dictionary.
		/// </summary>
		public ICollection<TValue> Values
		{
			get
			{
				return _innerDictionary.Values;
			}
		}


		/// <summary>
		/// Gets a value indicating whether the collection is read-only.
		/// </summary>
		public virtual bool IsReadOnly
		{
			get { return _isReadOnly; }
			internal set { _isReadOnly = value; }
		}

		#region internal Members
		internal void SetItemInternal(TKey key, TValue value)
		{
			this._innerDictionary[key] = value;
		}

		internal void ClearInternal()
		{
			this._innerDictionary.Clear();
		}

		internal bool RemoveInternal(TKey key)
		{
			return this._innerDictionary.Remove(key);
		}
		#endregion

		#region Private Members
		private IDictionary<TKey, TValue> _innerDictionary = new Dictionary<TKey, TValue>();
		private bool _isReadOnly;

		private void CheckIsReadOnly()
		{
			if (IsReadOnly) throw new InvalidOperationException("The collection is read-only.");
		}
		#endregion

		#region IEnumerable<KeyValuePair<TKey, TValue>> Members
		public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
		{
			return this._innerDictionary.GetEnumerator();
		}
		#endregion

		#region IEnumerable Members
		[SuppressMessage("Microsoft.Usage", "CA9888:DisposeObjectsCorrectly", Justification = "Returning the enumerator.")]
		IEnumerator IEnumerable.GetEnumerator()
		{
			return this._innerDictionary.GetEnumerator();
		}

		[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by runtime to get known types")]
		public static IEnumerable<Type> GetKnownParameterTypes()
		{
			return KnownTypesProvider.RetrieveKnownValueTypes();
		}
		#endregion

	}

	/// <summary>
	/// Represents collection of attribute values
	/// </summary>
	[KnownType("GetKnownParameterTypes")]
	[CollectionDataContract(Name = "AttributeCollection", Namespace = XmlNamespaces.V5.Contracts)]
	public sealed class AttributeCollection : System.Collections.ObjectModel.Collection<KeyValuePairOfstringanyType>
	{
		[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by runtime to get known types")]
		public static IEnumerable<Type> GetKnownParameterTypes()
		{
			return KnownTypesProvider.RetrieveKnownValueTypes();
		}

		public object this[String key]
		{
			get
			{
				foreach (KeyValuePairOfstringanyType item in this)
				{
					if (item.Key == key)
					{
						return item.Value;
					}
				}
				return null;
			}
			set
			{
				KeyValuePairOfstringanyType existingPair = TryGetPair(key);
				if (existingPair != null)
				{
					existingPair.Value = value;
				}
				else
				{
					Add(new KeyValuePairOfstringanyType { Key = key, Value = value });
				}
			}
		}

		private KeyValuePairOfstringanyType TryGetPair(string key)
		{
			foreach (KeyValuePairOfstringanyType item in this)
			{
				if (item.Key == key)
				{
					return item;
				}
			}
			return null;
		}

		public Boolean Contains(String key)
		{
			return (this[key] != null);
		}
	}

	/// <summary>
	/// Represents collection of attribute values formatted using user settings
	/// </summary>
	[CollectionDataContract(Name = "FormattedValueCollection", Namespace = XmlNamespaces.V5.Contracts)]
	public sealed class FormattedValueCollection : DataCollection<string, string>
	{
	}

	/// <summary>
	/// collection to provide error details
	/// </summary>
	[CollectionDataContract(Name = "ErrorDetailCollection", Namespace = XmlNamespaces.V5.Contracts)]
	//[Serializable]
	public sealed class ErrorDetailCollection : DataCollection<string, object>
	{
	}

	[DataContract(Name = "KeyValuePairOfstringanyType",
	Namespace = "http://schemas.datacontract.org/2004/07/System.Collections.Generic")]
	public class KeyValuePairOfstringanyType
	{
		[DataMember(Name = "key")]
		public String Key { get; set; }

		[DataMember(Name = "value")]
		public object Value { get; set; }
	}


	/// <summary>
	/// collection of fields
	/// </summary>
	[KnownType("GetKnownParameterTypes")]
	[CollectionDataContract(Name = "ParameterCollection", Namespace = XmlNamespaces.V5.Contracts)]
	public sealed class ParameterCollection : System.Collections.ObjectModel.Collection<KeyValuePairOfstringanyType>
	{
		[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by runtime to get known types")]
		public static IEnumerable<Type> GetKnownParameterTypes()
		{
			return KnownTypesProvider.RetrieveKnownValueTypes();
		}

		public object this[String key]
		{
			get
			{
				foreach (KeyValuePairOfstringanyType item in this)
				{
					if (item.Key == key)
					{
						return item.Value;
					}
				}
				return null;
			}
			set
			{
				KeyValuePairOfstringanyType existingPair = TryGetPair(key);
				if (existingPair != null)
				{
					existingPair.Value = value;
				}
				else
				{
					Add(new KeyValuePairOfstringanyType { Key = key, Value = value });
				}
			}
		}

		private KeyValuePairOfstringanyType TryGetPair(string key)
		{
			foreach (KeyValuePairOfstringanyType item in this)
			{
				if (item.Key == key)
				{
					return item;
				}
			}
			return null;
		}

		public Boolean Contains(String key)
		{
			return (this[key] != null);
		}
	}



	[SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Justification = "Class wraps a collection")]
	[DataContract(Name = "EntityCollection", Namespace = XmlNamespaces.V5.Contracts)]
	public sealed class EntityCollection : IExtensibleDataObject
	{
		public EntityCollection()
		{
		}

		public EntityCollection(IList<Entity> list)
		{
			this._entities = new DataCollection<Entity>(list);
		}

		[DataMember]
		public DataCollection<Entity> Entities
		{
			get
			{
				if (_entities == null)
				{
					_entities = new DataCollection<Entity>();
				}
				return _entities;
			}
			[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called via reflection")]

			internal set
			{
				_entities = value;
			}
		}

		[DataMember]
		public bool MoreRecords
		{
			get
			{
				return _moreRecords;
			}
			set
			{
				CheckIsReadOnly();
				_moreRecords = value;
			}
		}

		[DataMember]
		public string PagingCookie
		{
			get
			{
				return _pagingCookie;
			}
			set
			{
				CheckIsReadOnly();
				_pagingCookie = value;
			}
		}

		[DataMember]
		public string MinActiveRowVersion
		{
			get
			{
				return _minActiveRowVersion;
			}
			set
			{
				CheckIsReadOnly();
				_minActiveRowVersion = value;
			}
		}

		[DataMember]
		public int TotalRecordCount
		{
			get
			{
				return _totalRecordCount;
			}
			set
			{
				CheckIsReadOnly();
				_totalRecordCount = value;
			}
		}

		[DataMember]
		public bool TotalRecordCountLimitExceeded
		{
			get
			{
				return _totalRecordCountLimitExceeded;
			}
			set
			{
				CheckIsReadOnly();
				_totalRecordCountLimitExceeded = value;
			}
		}

		public Entity this[int index]
		{
			get
			{
				return this.Entities[index];
			}

			set
			{
				CheckIsReadOnly();
				this.Entities[index] = value;
			}
		}

		[DataMember]
		public string EntityName
		{
			get { return _entityName; }

			set
			{
				CheckIsReadOnly();
				_entityName = value;
			}
		}

		#region internal Members
		internal bool IsReadOnly
		{
			get { return _isReadOnly; }

			set { _isReadOnly = value; }
		}
		#endregion

		private string _entityName;
		private DataCollection<Entity> _entities;
		private bool _moreRecords;
		private string _pagingCookie;
		private string _minActiveRowVersion;
		private int _totalRecordCount;
		private bool _totalRecordCountLimitExceeded;
		private bool _isReadOnly;

		private void CheckIsReadOnly()
		{
			if (IsReadOnly) throw new InvalidOperationException("The collection is read-only.");
		}

		#region IExtensibleDataObject Implementation
		private ExtensionDataObject ExtensionDataObject;
		public ExtensionDataObject ExtensionData
		{
			get
			{
				return ExtensionDataObject;
			}
			set
			{
				ExtensionDataObject = value;
			}
		}
		#endregion IExtensibleDataObject Implementation
	}

	[CollectionDataContract(Name = "RelatedEntityCollection", Namespace = XmlNamespaces.V5.Contracts)]
	public sealed class RelatedEntityCollection : DataCollection<Relationship, EntityCollection>
	{
		public override bool IsReadOnly
		{
			get { return base.IsReadOnly; }

			internal set
			{
				base.IsReadOnly = value;

				foreach (var pair in this)
				{
					if (pair.Value != null)
					{
						pair.Value.IsReadOnly = true;
					}
				}
			}
		}
	}

	/// <summary>
	/// Collection of related entity ids
	/// </summary>
	[CollectionDataContract(Name = "EntityImageCollection", Namespace = XmlNamespaces.V5.Contracts)]
	public sealed class EntityImageCollection : DataCollection<string, Entity>
	{
	}

	[CollectionDataContract(Name = "EntityReferenceCollection", Namespace = XmlNamespaces.V5.Contracts)]
	public sealed class EntityReferenceCollection : DataCollection<EntityReference>
	{
		public EntityReferenceCollection() { }
		public EntityReferenceCollection(IList<EntityReference> list)
			: base(list)
		{
		}
	}

	[CollectionDataContract(Name = "RelationshipQueryCollection", Namespace = XmlNamespaces.V5.Contracts)]
	public sealed class RelationshipQueryCollection : DataCollection<Relationship, Query.QueryBase>
	{
	}
}
